package cn.seaboot.flake.core;

import cn.seaboot.commons.lang.Warning;
import cn.seaboot.flake.jdbc.MapResultSetExtractor;
import cn.seaboot.flake.jdbc.ObjectResultSetExtractor;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.List;
import java.util.Map;

/**
 * 一个更易于使用的 JdbcTemplate
 * <p>
 * 针对当前项目的设计理念，拓展一些常用的 API
 *
 * @author Mr.css
 * @version 2025-05-15 9:55
 */
public class SqlHelper extends JdbcTemplate {

    // 分词器
    public static final FlakeTokenizer DEF_TOKENIZER = new PreparedTokenizer();
    // 模版引擎
    public static final FlakeTemplate DEF_TEMPLATE = new BeetlesTemplate();

    /**
     * Construct a new JdbcTemplate for bean usage.
     * <p>Note: The DataSource has to be set before using the instance.
     *
     * @see #setDataSource
     */
    public SqlHelper() {
    }

    /**
     * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
     * <p>Note: This will not trigger initialization of the exception translator.
     *
     * @param dataSource the JDBC DataSource to obtain connections from
     */
    public SqlHelper(DataSource dataSource) {
        super(dataSource);
    }

    /**
     * Construct a new JdbcTemplate, given a DataSource to obtain connections from.
     * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator
     * will be triggered.
     *
     * @param dataSource the JDBC DataSource to obtain connections from
     * @param lazyInit   whether to lazily initialize the SQLExceptionTranslator
     */
    public SqlHelper(DataSource dataSource, boolean lazyInit) {
        super(dataSource, lazyInit);
    }

    /**
     * 预编译 SQL
     * <p>
     * 这个函数是核心，负责将 SQL 模版，渲染成业务上能用的查询语句；
     * 经过处理才查询语句，可以被 <code>PreparedStatement<code/> 直接直接使用；
     * <p>
     * 这意味着，实际上可以对接大部分 JDBC 实现类。
     *
     * @param sql  SQL 模版
     * @param args 处理好的参数
     * @return 可供执行的SQL和参数
     */
    public PreparedSql process(String sql, Map<String, Object> args) {
        // 模版引擎处理基本逻辑
        String tpl = DEF_TEMPLATE.process(sql, args);
        // 去除所有占位符
        return DEF_TOKENIZER.process(tpl, args);
    }

    /**
     * 结果集查询成 map
     *
     * @param sql sql
     * @return map or null
     */
    public Map<String, Object> queryAsMap(String sql) {
        List<Map<String, Object>> list = this.queryAsListMap(sql);
        if (list.isEmpty()) {
            return null;
        } else if (list.size() == 1) {
            return list.get(0);
        } else {
            throw new TooManyResultsException("Too many results, when executing query: " + sql);
        }
    }

    /**
     * 结果集查询成 map
     *
     * @param sql sql
     * @return not-nullable list
     */
    @SuppressWarnings(Warning.ALL)
    public List<Map<String, Object>> queryAsListMap(String sql, Object... args) {
        return super.query(sql, new MapResultSetExtractor<>(),args);
    }

    /**
     * 将结果集封装成 List
     *
     * @param sql     sql
     * @param type    element type
     * @param mapping result-map
     * @param args    sql-parameter
     * @param <T>     element-type
     * @return not null list
     */
    @SuppressWarnings(Warning.ALL)
    public <T> List<T> queryAsList(String sql, Class<T> type, Map<String, String> mapping, Object... args) {
        return super.query(sql, new ObjectResultSetExtractor<>(type, mapping), args);
    }

    /**
     * 将结果集封装成 Object
     *
     * @param sql     sql
     * @param type    element type
     * @param mapping result-map
     * @param args    sql-parameter
     * @param <T>     element-type
     * @return not null list
     */
    public <T> T queryAsOne(String sql, Class<T> type, Map<String, String> mapping, Object... args) {
        List<T> list = this.queryAsList(sql, type, mapping, args);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Receive too many result when execute: " + sql);
        } else {
            return null;
        }
    }
}
